/// 
/// MaDAson - Toolkit for Model Driven Software Development
/// 
/// Copyright [2006] Marcus Lankenau
/// 
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///  
///     http://www.apache.org/licenses/LICENSE-2.0
///  
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// 

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Madason.MetaModel;

namespace Madason.MetaModel.Interfaces
{



	/// <summary>
	/// Import Module for OMG XMI 1.1 Format
	/// 
	/// Author: Marcus Lankenau
	/// 
	/// </summary>
	public class ImportXmi11 : IMetaModelImport
	{
		Model model = null;
		XmlNamespaceManager xmlNamespaces = null;
		XmlNode rootNode = null;


		protected static string GetAttr(XmlNode node, String name)
		{
			XmlAttribute attr = node.Attributes[name];
			if (attr == null) return null;
			return attr.Value;
		}

        protected static string GetXPathValue(XmlNode parent, String xpath)
        {
            XmlNode node = parent.SelectSingleNode(xpath);
            if (node == null) return null;
            return node.InnerText;
        }


		/// <summary>
		/// Handle all stuff, wich applies to all model elements like stereotypes, tagged values...
		/// </summary>
		/// <param name="me"></param>
		/// <param name="node"></param>
		public void HandleModelElement(ModelElement me, XmlNode node)
		{
			me.Name = GetAttr(node, "name");
			me.Id = GetAttr(node, "xmi.id");
			
			// look for stereotypes 
			foreach (XmlNode stereotypeNode in node.SelectNodes("UML:ModelElement.stereotype/UML:Stereotype", xmlNamespaces))																 
			{
				
				string idref = GetAttr(stereotypeNode, "xmi.idref");
				if (idref != null)
				{
					XmlNode stereotypeDef = rootNode.SelectSingleNode("//UML:Stereotype[@xmi.id='" + idref + "']", xmlNamespaces);
					if (stereotypeNode == null) throw new Exception("can't find Stereotype with id " + idref);
					me.StereotypeName = GetAttr(stereotypeDef, "name");
				}
			}

			// look for stereotype reference
			if (GetAttr(node, "stereotype") != null)
			{
				string idref = GetAttr(node, "stereotype");
				XmlNode stereotypeDef = rootNode.SelectSingleNode("//UML:Stereotype[@xmi.id='" + idref + "']", xmlNamespaces);
				if (stereotypeDef != null)
					me.StereotypeName = GetAttr(stereotypeDef, "name"); 
			}

			// look for embedded stereotype
			XmlNode embeddedStereotype = node.SelectSingleNode("UML:Stereotype", xmlNamespaces);
			if (embeddedStereotype != null)
			{
				me.StereotypeName = GetAttr(embeddedStereotype, "name");
			}

			// look for tagged values
			foreach (XmlNode tvNode in node.SelectNodes("UML:TaggedValue", xmlNamespaces))
			{
				TaggedValue tv = new TaggedValue();
				tv.Key = GetAttr(tvNode, "tag");
				tv.Value = GetAttr(tvNode, "value");
				me.TaggedValues.Add(tv);
			}

			


			// look for tagged values
			foreach (XmlNode tvNode in node.SelectNodes("UML:ModelElement.taggedValue/UML:TaggedValue", xmlNamespaces))
			{
				TaggedValue tv = new TaggedValue();
				tv.Key = GetAttr(tvNode, "name");
				XmlNode valueNode = tvNode.SelectSingleNode("UML:TaggedValue.dataValue", xmlNamespaces);
				if (valueNode == null) continue;
				tv.Value = valueNode.InnerText;
				me.TaggedValues.Add(tv);
			}
		}


		/// <summary>
		/// Handle class
		/// </summary>
		/// <param name="ns"></param>
		/// <param name="node"></param>
		public void HandleClass(Namespace ns, XmlNode node)
		{
			Class cls = new Class();
			cls.Namespace = ns;
			ns.Classes.Add(cls);
			HandleModelElement(cls, node);			

			// handle attributes
			foreach (XmlNode attrNode in node.SelectNodes("UML:Classifier.feature/UML:Attribute", xmlNamespaces))
			{
				HandleAttribute(cls,attrNode);
			}

			// handle operations
			foreach (XmlNode opNode in node.SelectNodes("UML:Classifier.feature/UML:Operation", xmlNamespaces))
			{
				HandleOperation(cls, opNode);
			}

			// handle statemachine
			foreach (XmlNode smNode in node.SelectNodes("UML:Namespace.ownedElement/UML:StateMachine", xmlNamespaces))
			{
				HandleStatemachine(cls, smNode);
			}


		}

		public void HandleState(State owner, XmlNode node)
		{
			State state = new State();
			state.Parent = owner;
			owner.States.Add(state);
			HandleModelElement(state, node);

			string kind = GetAttr(node, "kind");
			if (kind == "initial") state.StateType = StateType.Initial;
			else if (kind == "branch") state.StateType = StateType.Branch;
			else if (node.LocalName == "FinalState") state.StateType = StateType.Final;
			else state.StateType = StateType.Simple;

			foreach (XmlNode actionNode in node.SelectNodes("UML:State.entry/UML:UninterpretedAction", xmlNamespaces))
			{
				StateAction action = new StateAction();
				state.EntryActions.Add(action);
				HandleModelElement(action, actionNode);
			}
			foreach (XmlNode actionNode in node.SelectNodes("UML:State.exit/UML:UninterpretedAction", xmlNamespaces))
			{
				StateAction action = new StateAction();
				state.ExitActions.Add(action);
				HandleModelElement(action, actionNode);
			}
		}

		public void HandleTransition(StateMachine sm, XmlNode node)
		{
			StateTransition trans = new StateTransition();

			string sourceName = GetAttr(node, "source");
			string targetName = GetAttr(node, "target");
			State sourceState = sm.FindStateByID(sourceName);
			State targetState = sm.FindStateByID(targetName);

			trans.Source = sourceState;
			trans.Target = targetState;
			sourceState.Transitions.Add(trans);
			sm.Transitions.Add(trans);

			HandleModelElement(trans, node);

			// look for guards
			XmlNode guardNode = node.SelectSingleNode("UML:Transition.guard/UML:Guard/UML:Guard.expression/UML:BooleanExpression", xmlNamespaces);
			if (guardNode != null)
			{
				trans.Guard = GetAttr(guardNode, "body");
			}

			// look for trigger
			foreach (XmlNode callNode in node.SelectNodes("UML:Transition.trigger/UML:CallEvent", xmlNamespaces))
			{
				StateTransitionTrigger trigger = new StateTransitionTrigger();
				trigger.Transition = trans;
				trans.Trigger.Add(trigger);
				HandleModelElement(trigger, callNode);
			}

			// look for actions
			foreach (XmlNode actionNode in node.SelectNodes("UML:Transition.effect/UML:ActionSequence/UML:ActionSequence.action/UML:UninterpretedAction", xmlNamespaces))
			{
				StateAction action = new StateAction();
				trans.Actions.Add(action);
				HandleModelElement(action, actionNode);
			}
		}

		public void HandleStatemachine(ModelElement owner, XmlNode node)
		{
			StateMachine sm = new StateMachine();
			if (owner is Class)
			{
				sm.Class = owner as Class;
				(owner as Class).StateMachines.Add(sm);
			}
			HandleModelElement(sm, node);

			// staruml always stores a statemachine as a single parent-state named TOP
			// wich we want to ignore
			XmlNode topNode = node.SelectSingleNode(@"UML:StateMachine.top/UML:CompositeState[@name=""TOP""]", xmlNamespaces);
			if (topNode != null)
			{
				string searcharg =
					"UML:CompositeState.subvertex/UML:Pseudostate|" +
					"UML:CompositeState.subvertex/UML:SimpleState|" +
					"UML:CompositeState.subvertex/UML:FinalState";
				foreach (XmlNode stateNode in topNode.SelectNodes(searcharg, xmlNamespaces))
				{
					HandleState(sm, stateNode);
				}
			}
			else
				throw new ApplicationException("can't find TOP-state");

			foreach (XmlNode transNode in node.SelectNodes("UML:StateMachine.transitions/UML:Transition", xmlNamespaces))
			{
				HandleTransition(sm, transNode);
			}
		}


		/// <summary>
		/// Handle Stereotypes
		/// </summary>
		/// <param name="ns"></param>
		/// <param name="node"></param>
		public void HandleStereotype(Namespace ns, XmlNode node)
		{
			string name = GetAttr(node, "name");
			string extendedElements = GetAttr(node, "extendedElement");
			if (extendedElements != null)
			{
				foreach (string id in extendedElements.Split(" ".ToCharArray()))
				{
					ModelElement modelElement = model.FindModelElementById(id);
					if (modelElement != null)
						modelElement.StereotypeName = name;
				}
			}
		}

		/// <summary>
		/// Handle Generalization
		/// </summary>
		/// <param name="ns"></param>
		/// <param name="node"></param>
		public void HandleGeneralization(Namespace ns, XmlNode node)
		{
			string child = GetAttr(node, "child");
			string parent = GetAttr(node, "parent");

			if (child != null && parent != null)
			{
				Class childClass = model.FindClassById(child);

				if (childClass != null)
					childClass.SuperClassID = parent; 
			}
		}

		/// <summary>
		/// Handle Tagged Value
		/// </summary>
		/// <param name="node"></param>
		public void HandleTaggedValue(XmlNode node)
		{
			string modelElementId = GetAttr(node, "modelElement");

			TaggedValue tv = new TaggedValue();
			tv.Key = GetAttr(node, "tag");
			tv.Value = GetAttr(node, "value");
			

			ModelElement me = model.FindModelElementById(modelElementId);
			if (me != null)
				me.TaggedValues.Add(tv);
		}


		/// <summary>
		/// Handle Associations
		/// </summary>
		/// <param name="node"></param>
		public void HandleAssociation(XmlNode node)
		{
			XmlNodeList nodeList = node.SelectNodes("//UML:AssociationEnd", xmlNamespaces);
			if (nodeList.Count != 2) return;

			string type1id = GetAttr(nodeList[0], "type");
			string type2id = GetAttr(nodeList[1], "type");
			if (type1id == null || type2id == null) return;

			Class cls1 = model.FindClassById(type1id);
			Class cls2 = model.FindClassById(type2id);

			Association assoc1 = new Association();
			Association assoc2 = new Association();
			assoc1.Class = cls1;
			assoc2.Class = cls2;
			cls1.Associations.Add(assoc1);
			cls2.Associations.Add(assoc2);
			assoc1.TargetClassId = type2id;
			assoc2.TargetClassId = type1id;
			assoc1.Name = GetAttr(nodeList[1], "name");
			assoc2.Name = GetAttr(nodeList[0], "name");
			
			XmlNode cardNode1 = nodeList[0].SelectSingleNode("UML:AssociationEnd.multiplicity/UML:Multiplicity/UML:Multiplicity.range/UML:MultiplicityRange", xmlNamespaces);
			if (cardNode1 != null)
			{
				string upper = GetAttr(cardNode1, "upper");
				assoc2.CardinalityText = upper;
			}
			XmlNode cardNode2 = nodeList[1].SelectSingleNode("UML:AssociationEnd.multiplicity/UML:Multiplicity/UML:Multiplicity.range/UML:MultiplicityRange", xmlNamespaces);
			if (cardNode2 != null)
			{
				string upper = GetAttr(cardNode2, "upper");
				assoc1.CardinalityText = upper;
			}			
		}


		public void HandleAttribute(Class cls, XmlNode node)
		{
			Attribute attr = new Attribute();
			HandleModelElement(attr, node);
			cls.Attributes.Add(attr);
			attr.Class = cls;
			attr.TypeId = GetAttr(node, "type");

			XmlNode specialType = node.SelectSingleNode("UML:StructuralFeature.type/UML:Classifier", xmlNamespaces);
			if (specialType != null)
				attr.TypeId = GetAttr(specialType, "xmi.idref");
		}

		public void HandleOperation(Class cls, XmlNode node)
		{
			Operation op = new Operation();
			HandleModelElement(op, node);
			cls.Operations.Add(op);
			op.Class = cls;


			foreach (XmlNode paramNode in node.SelectNodes("UML:BehavioralFeature.parameter/UML:Parameter", xmlNamespaces))
			{
				string kind = GetAttr(paramNode, "kind");

				if (kind == "return")
				{
					op.ReturnTypeId = GetAttr(paramNode, "type");
                    if (op.ReturnTypeId == null)
                    {
                        XmlNode typeNode = paramNode.SelectSingleNode("UML:Parameter.type/UML:Classifier", xmlNamespaces);
                        if (typeNode != null)
                            op.ReturnTypeId = typeNode.Attributes["xmi.idref"].Value;
                    }   
				}
				else
				{
					Parameter param = new Parameter();
					param.Operation = op;
					op.Parameters.Add(param);
                    
					HandleModelElement(param, paramNode);
					param.TypeId = GetAttr(paramNode, "type");
                    if (param.TypeId == null)
                    {
                        XmlNode typeNode = paramNode.SelectSingleNode("UML:Parameter.type/UML:Classifier", xmlNamespaces);
                        if (typeNode != null)
                            param.TypeId = typeNode.Attributes["xmi.idref"].Value;
                    }  
				}

			}
		}

		/// <summary>
		/// Handle Namsespace/Package element
		/// </summary>
		/// <param name="ns"></param>
		/// <param name="node"></param>
		public void HandleNamespace(Namespace ns, XmlNode node)
		{
			HandleModelElement(ns, node);

			foreach (XmlNode packageNode in node.SelectNodes("UML:Namespace.ownedElement/UML:Package|UML:Package", xmlNamespaces))
			{
				Namespace childns = new Namespace();
				ns.Namespaces.Add(childns);
				if (packageNode.Attributes["name"] != null)
				{
					childns.Name = packageNode.Attributes["name"].Value;
					HandleNamespace(childns, packageNode);
				}
			}

			// handle classes
			foreach (XmlNode classNode in node.SelectNodes("UML:Namespace.ownedElement/UML:Class|UML:Class|UML:DataType|UML:Namespace.ownedElement/UML:DataType", xmlNamespaces))
			{
				HandleClass(ns, classNode);
			}



			// handle stereotypes
			foreach (XmlNode stereotypeNode in node.SelectNodes("UML:Stereotype", xmlNamespaces))
			{
				HandleStereotype(ns, stereotypeNode);
			}


			// handle generalization
			foreach (XmlNode generalizationNode in node.SelectNodes("UML:Generalization", xmlNamespaces))
			{
				HandleGeneralization(ns, generalizationNode);
			}

			// look for tagged values
			foreach (XmlNode tvNode in node.SelectNodes("UML:TaggedValue", xmlNamespaces))
			{
				HandleTaggedValue(tvNode);
			}

            // handle associations
            foreach (XmlNode assocNode in node.SelectNodes("UML:Namespace.ownedElement/UML:Association|UML:Association", xmlNamespaces))
			{
				HandleAssociation(assocNode);
			}

		}



		/// <summary>
		/// Import Xmi11 File
		/// </summary>
		/// <param name="reader"></param>
		/// <returns></returns>
		public Model Import(System.IO.TextReader reader)
		{
			model = new Model();

			NameTable nameTable = new NameTable();
			XmlDocument xmldoc = new XmlDocument(nameTable);
			xmlNamespaces = new XmlNamespaceManager(nameTable);
			
			xmldoc.Load(reader);
			rootNode = xmldoc.DocumentElement;

			XmlNode testModel = (XmlNode)xmldoc.DocumentElement.SelectSingleNode("//*[local-name()='Model']", xmlNamespaces);
			string umlUri = testModel.NamespaceURI;
			xmlNamespaces.AddNamespace("UML", umlUri);						



			XmlNode modelElement = xmldoc.DocumentElement.SelectSingleNode("//UML:Model", xmlNamespaces);
			
			// this is just for the testcases, should't happen in real world scenaria
			if (modelElement == null) modelElement = xmldoc.DocumentElement;


			HandleNamespace(model, modelElement);

			return model;
		}
	}
}
